#include "icmp.h"
#include "ip.h"
#include "rtable.h"
#include "arp.h"
#include "base.h"
#include "log.h"
#include <stdlib.h>
#include <assert.h>

// icmp_send_packet has two main functions:
// 1.handle icmp packets sent to the router itself (ICMP ECHO REPLY).
// 2.when an error occurs, send icmp error packets.
// Note that the structure of these two icmp packets is different, you need to malloc different sizes of memory.
// Some function and macro definitions in ip.h/icmp.h can help you.
void icmp_send_packet(const char *in_pkt, int len, u8 type, u8 code)
{
	// assert(0 && "TODO: function icmp_send_packet not implemented!");
	// original packet ip_hdr
    struct iphdr *ip_hdr = packet_to_ip_hdr(in_pkt);
	struct icmphdr *icmp_hdr =NULL;
    char *icmp_pkt;                                  // 用于存储新构建的 ICMP 数据包
    int icmp_len;                                    // ICMP 数据包长度

    if (type == ICMP_ECHOREPLY) {
        log(DEBUG,"icmp sending ECHOREPLY packet");
		// 包长度相同
		icmp_len=len;
        // 分配内存用于回显应答
        icmp_pkt = (char *)malloc(icmp_len);
        if (!icmp_pkt) {
            log(ERROR,"fail to alloc memory for icmp packet!");
            return;
		}

        memcpy(icmp_pkt, in_pkt, icmp_len); // 复制接收到的 ICMP 包

        // 设置以太网头部
        struct ether_header *eh=(struct ether_hdr*)(icmp_pkt);
       // 交换目标和源 MAC 地址
        u8 tmp_mac[ETH_ALEN];
        memcpy(tmp_mac, eh->ether_shost, ETH_ALEN);
        memcpy(eh->ether_shost, eh->ether_dhost, ETH_ALEN);
        memcpy(eh->ether_dhost, tmp_mac, ETH_ALEN);


        // 设置以太网协议类型为 IP
        eh->ether_type = htons(ETH_P_IP);
                
        // 设置ip头

		struct iphdr *ip=packet_to_ip_hdr(icmp_pkt);
        // 重新设置ip头，要回显,注意init之中会做字节序转换这里直接传原始。
        ip_init_hdr(ip,ntohl(ip->daddr),ntohl(ip->saddr),icmp_len-ETHER_HDR_SIZE,IPPROTO_ICMP);
       
		// 准备更改icmp_hdr的一些字段，其他都复制
		icmp_hdr = (struct icmphdr *)IP_DATA(ip);
        

        icmp_hdr->type = type;   // 修改消息类型为回显应答
		icmp_hdr->code = code; // 正常回显，不用关心。
        icmp_hdr->checksum = 0;           // 清零校验和
        

        log(DEBUG,"icmp EchoReply packet sent");
    } else if (type == ICMP_DEST_UNREACH) {
        // 处理 ICMP Destination Unreachable 消息
        log(DEBUG,"icmp sending DEST UNREACH packet");
        icmp_len = ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + ICMP_HDR_SIZE + IP_BASE_HDR_SIZE + 8; 
        // ETHER header + 新 IP header + ICMP header + 原始 IP header + 原始 payload 前 8 字节

        icmp_pkt = (char *)malloc(icmp_len);
        if (!icmp_pkt) {
            log(ERROR, "Failed to allocate memory for ICMP packet!");
            return;
        }

        // 清零新包内存
        memset(icmp_pkt, 0, icmp_len);

        // 复制以太网帧头部
        memcpy(icmp_pkt, in_pkt, ETHER_HDR_SIZE);
        struct ether_header * eh=(struct ether_header*)(icmp_pkt);

         // 重新填装以太头地址
        u8 tmp_mac[ETH_ALEN];
        memcpy(tmp_mac, eh->ether_shost, ETH_ALEN);
        memcpy(eh->ether_shost, eh->ether_dhost, ETH_ALEN);
        memcpy(eh->ether_dhost, tmp_mac, ETH_ALEN);

        // 构造 IP 头部
        

        struct iphdr *ip = packet_to_ip_hdr(icmp_pkt);
        ip_init_hdr(ip, ntohl(ip_hdr->daddr), ntohl(ip_hdr->saddr), icmp_len - ETHER_HDR_SIZE, IPPROTO_ICMP);
        

        // 构造 ICMP 头部
        icmp_hdr = (struct icmphdr *)IP_DATA(ip);
        icmp_hdr->type = type;    // ICMP 类型：Destination Unreachable
        icmp_hdr->code = code;    // ICMP 错误代码
        icmp_hdr->checksum = 0;   // 校验和，稍后计算

        // 复制原始 IP header 和前 8 字节的 payload
        char *original_ip = (char *)(icmp_pkt + ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + ICMP_HDR_SIZE);
        memcpy(original_ip, in_pkt + ETHER_HDR_SIZE, IP_BASE_HDR_SIZE + 8);

    }else if (type == ICMP_TIME_EXCEEDED) {
        log(DEBUG, "icmp sending TIME EXCEEDED packet");
        icmp_len = ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + ICMP_HDR_SIZE + IP_BASE_HDR_SIZE + 8;
        // ETHER header + 新 IP header + ICMP header + 原始 IP header + 原始 payload 前 8 字节

        icmp_pkt = (char *)malloc(icmp_len);
        if (!icmp_pkt) {
            log(ERROR, "Failed to allocate memory for ICMP packet!");
            return;
        }

        // 清零新包内存
        memset(icmp_pkt, 0, icmp_len);

        // 复制以太网帧头部
        memcpy(icmp_pkt, in_pkt, ETHER_HDR_SIZE);
        struct ether_header *eh = (struct ether_header *)(icmp_pkt);

        // 重新填装以太头地址
        u8 tmp_mac[ETH_ALEN];
        memcpy(tmp_mac, eh->ether_shost, ETH_ALEN);
        memcpy(eh->ether_shost, eh->ether_dhost, ETH_ALEN);
        memcpy(eh->ether_dhost, tmp_mac, ETH_ALEN);

        // 构造 IP 头部
        struct iphdr *ip = packet_to_ip_hdr(icmp_pkt);
        ip_init_hdr(ip, ntohl(ip_hdr->daddr), ntohl(ip_hdr->saddr), icmp_len - ETHER_HDR_SIZE, IPPROTO_ICMP);

        // 构造 ICMP 头部
        icmp_hdr = (struct icmphdr *)IP_DATA(ip);
        icmp_hdr->type = type; // ICMP 类型：Time Exceeded
        icmp_hdr->code = code; // ICMP 错误代码
        icmp_hdr->checksum = 0; // 校验和，稍后计算

        // 复制原始 IP header 和前 8 字节的 payload
        char *original_ip = (char *)(icmp_pkt + ETHER_HDR_SIZE + IP_BASE_HDR_SIZE + ICMP_HDR_SIZE);
        memcpy(original_ip, in_pkt + ETHER_HDR_SIZE, IP_BASE_HDR_SIZE + 8);
    }
    

    // 最后再更新checksum
    // 注意，对于正常的包来说,icmp_len就能满足要求了。但是对于unreach，传入的参数有点变化。
    icmp_hdr->checksum = icmp_checksum(icmp_hdr,icmp_len);
    log(INFO,"len is %d",icmp_len);

    // ip_send_packet will use longest prefix match to find the proper iface to do. So all you need is to set proper dst ip and dst mac
    ip_send_packet(icmp_pkt, icmp_len); // 发送数据包

}
